package framework.service.cluster;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import framework.Trait;
import framework.server.Server;
import framework.server.ServerException;
import framework.service.MessageService;
import framework.service.client.ClientManager;
import framework.service.client.ClientMessage;
import framework.service.client.ClientRequest;
import framework.service.client.ClientService;

/**
 * Cluster.<br>
 *
 * @author	Orlin Tomov
 * @version	0.2
 *
 */
public class Cluster extends MessageService implements ClientService {

	/** server names */
	public List<String> servers = new ArrayList<String>();
	/** client sockets */
	private List<Dispatcher> cluster = new ArrayList<Dispatcher>();

	/**
	 * @param	container
	 * @param	serviceName
	 */
	public Cluster(Trait container, String serviceName) {
		super(container, serviceName);
	}

	@Override
	protected Server getContainer() {
		return (Server) super.getContainer();
	}

	/**
	 * Returns the info of the service.
	 * 
	 * @return	service info
	 */
	@Override
	public String info() {
		String result = super.info();
		synchronized (cluster) {
			if (cluster.size() > 0) {
				result += "cluster:\n";
				for (int i = 0; i < cluster.size(); i++) {
					Dispatcher dispatcher = cluster.get(i);
					result += dispatcher.getServiceName() + "\n";
				}
			} else {
				result += "standalone\n";
			}
		}
		return result;
	}

	/** do nothing */
	@Override
	public void init() throws ServerException {
	}

	/** Opens sockets to servers into the cluster */
	@Override
	public void starting() {
		synchronized (cluster) {
			cluster.clear();
			ClientManager clientManager = getContainer().getService(ClientManager.class.getName());
			int clientPort = ClientManager.IP_PORT;
			if (clientManager != null) {
				clientPort = clientManager.getPort();
				clientManager.addService(getName(), true);
			}
			for (int i = 0; i < servers.size(); i++) {
				String host = servers.get(i);
				int port;
				if (host.indexOf(":") > 0) {
					port = Integer.parseInt(host.substring(host.indexOf(":") + 1));
					host = host.substring(0, host.indexOf(":"));
					try {
						host = InetAddress.getByName(host).getHostName();
					} catch (UnknownHostException exception) {
						/**/log.warn("Unknown host " + host);
						continue;
					}
				} else {
					/**/log.warn(getServiceName() + " unknown port for server on " + host);
					continue;
				}
				try {
					Dispatcher dispatcher = getContainer().getService(host + ":" + port);
					if (dispatcher != null) {
						continue;
					}
					dispatcher = new Dispatcher(getContainer(), host, port, clientPort);
					dispatcher.starting();
					while (dispatcher.remote == null) {
						try {
							sleep(100L);
						} catch (InterruptedException exception) {
							/**/log.error(exception.getMessage(), exception);
						}
					}
					getContainer().addService(dispatcher);
					/**/log.info(dispatcher.remote + " on " + host + ":" + port);
					cluster.add(dispatcher);
				} catch (IOException exception) {
					/**/log.warn(exception.getMessage());
				}
			}
		}
		super.starting();
	}

	/**
	 * Closes sockets to the servers in the cluster
	 */
	@Override
	public void stoping() {
		super.stoping();
		synchronized (cluster) {
			for (int i = 0; i < cluster.size(); i++) {
				Dispatcher dispatcher = cluster.get(i);
				dispatcher.stoping();
			}
			cluster.clear();
		}
	}

	public void addDispatcher(Dispatcher dispatcher) {
		synchronized (cluster) {
			getContainer().addService(dispatcher);
			cluster.add(dispatcher);
		}
	}

	@Override
	public void postProcess() {
		synchronized (cluster) {
			if (cluster.size() == 0) {
				try {
					sleep(100L);
				} catch (InterruptedException exception) {
					/**/log.error(exception.getMessage(), exception);
				}
				return;
			}
			for (int i = cluster.size(); i > 0; i--) {
				Dispatcher dispatcher = cluster.get(i - 1);
				if (dispatcher.isRunning()) {
					try {
						sleep(100L);
					} catch (InterruptedException exception) {
						/**/log.error(exception.getMessage(), exception);
					}

				} else {
					/**/log.info("remove server " + dispatcher.getServiceName());
					cluster.remove(dispatcher);
					getContainer().remove(dispatcher);
				}
			}
		}
	}

	@Override
	public void process(ClientMessage message) {
		ClientRequest clientRequest = message.getClientRequest();
		if ((clientRequest.lines.size() > 0) && ((String)clientRequest.lines.get(0)).startsWith(Server.version)) {
			String host = clientRequest.getHost();
			String port = clientRequest.getClientPort();
			Dispatcher dispatcher = getContainer().getService(host + ":" + port);
			if (dispatcher != null) {
				return;
			}
			try {
				dispatcher = new Dispatcher(getContainer(), clientRequest);
				dispatcher.starting();
				while (dispatcher.remote == null) {
					try {
						sleep(100L);
					} catch (InterruptedException exception) {
						/**/log.error(exception.getMessage(), exception);
					}
				}
				addDispatcher(dispatcher);
				log.info(dispatcher.remote + " on " + dispatcher.getServiceName());
				return;
			} catch (IOException exception) {
				/**/log.error(exception.getMessage(), exception);
			}
		} else {
			ClientManager clientManager = getContainer().getService(ClientManager.class.getName());
			clientManager.nextClient(message, getName());
		}
	}

	public void process(ReconnectMessage message) {
		String link = message.getSender();
		String host = link.substring(0, link.indexOf(":"));
		int port = Integer.parseInt(link.substring(link.indexOf(":") + 1));
		ClientManager clientManager = getContainer().getService(ClientManager.class.getName());
		int clientPort = ClientManager.IP_PORT;
		if (clientManager != null) {
			clientPort = clientManager.getPort();
		}
		Dispatcher dispatcher = getContainer().getService(host + ":" + port);
		if (dispatcher != null) {
			return;
		}
		try {
			dispatcher = new Dispatcher(getContainer(), host, port, clientPort);
			dispatcher.starting();
			while (dispatcher.remote == null) {
				try {
					sleep(100L);
				} catch (InterruptedException exception) {
					/**/log.error(exception.getMessage(), exception);
				}
			}
			addDispatcher(dispatcher);
			log.info(dispatcher.remote + " on " + dispatcher.getServiceName());
		} catch (IOException e) {
		}
	}

	public void process(ForwardMessage message) {
		if (cluster.size() > 0) {
			String first = cluster.get(0).getName();
			StringBuffer next = new StringBuffer();
			for (int i = 1; i < cluster.size(); i++) {
				if (next.length() > 0) {
					next.append(",");
				}
				next.append(cluster.get(i).getName());
			}
			getContainer().sendMessage(message, next.toString(), first);
		}
	}
}
